#include <Windows.h>
#include <stdio.h>
#include <glut.h>

#include "markerInfo.h"

// user draw function
void (* DrawFunctionPointer)(void) = 0;
// user transform function
void (* TransformFunctionPointer)(void) = 0;
// user frame function (called before everything each frame displayed)
void (* PreDisplayFunctionPointer)(void) = 0;
// user keyboard function
void (* KeyboardFunctionPointer)(unsigned char key) = 0;
// user finalize function
void (* FinalizeCallbackPointer)(void) = 0;

// mouse state
int		MousePrevPos[2] = {0, 0},
		MouseButtons[3] = {GLUT_UP, GLUT_UP, GLUT_UP};
// camera translation and rotation
double	CameraTransl[3],
		CameraRotate[3];
// flag
bool	Running = false;
// lightning
bool	EnableEffects = false;
// text writing
HFONT	TextFont;
HFONT	TextOldFont;
GLuint	TextBase;
HDC     TextHdc;

void camera() {
	glTranslated(CameraTransl[0], CameraTransl[1], CameraTransl[2]);
	glRotated(CameraRotate[0], 1, 0, 0);
	glRotated(CameraRotate[1], 0, 1, 0);
	glRotated(CameraRotate[2], 0, 0, 1);
}

void drawOrigin() {
	glColor3d(1.0, 1.0, 1.0);

	// cube frame
	glNormal3d(0.0, 0.0, 0.0);
	glBegin(GL_LINE_STRIP);
		glVertex3d(-250.0, -250.0, -250.0);
		glVertex3d( 250.0, -250.0, -250.0);
		glVertex3d( 250.0,  250.0, -250.0);
		glVertex3d(-250.0,  250.0, -250.0);
		glVertex3d(-250.0, -250.0, -250.0);
	glEnd();

	glBegin(GL_LINE_STRIP);
		glVertex3d(-250.0, -250.0,  250.0);
		glVertex3d( 250.0, -250.0,  250.0);
		glVertex3d( 250.0,  250.0,  250.0);
		glVertex3d(-250.0,  250.0,  250.0);
		glVertex3d(-250.0, -250.0,  250.0);
	glEnd();

	glBegin(GL_LINES);
		glVertex3d(-250.0, -250.0, -250.0);
		glVertex3d(-250.0, -250.0,  250.0);
		glVertex3d( 250.0, -250.0, -250.0);
		glVertex3d( 250.0, -250.0,  250.0);
		glVertex3d( 250.0,  250.0, -250.0);
		glVertex3d( 250.0,  250.0,  250.0);
		glVertex3d(-250.0,  250.0, -250.0);
		glVertex3d(-250.0,  250.0,  250.0);
	glEnd();

	// x y and z axis
	glBegin(GL_LINES);
		glColor3d(1, 0, 0);
		glVertex3d(  0.0, 0.0, 0.0);
		glVertex3d(300.0, 0.0, 0.0);
		
		glColor3d(0, 1, 0);
		glVertex3d(  0.0, 0.0, 0.0);
		glVertex3d(0.0, 300.0, 0.0);
		
		glColor3d(0, 0, 1);
		glVertex3d(  0.0, 0.0, 0.0);
		glVertex3d(0.0, 0.0, 300.0);
	glEnd();
	
	// letters
	glPushMatrix();
	glTranslated(310.0, 0.0, 0.0);
	glColor3d(1, 0, 0);
	glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, 'X');
	glPopMatrix();
	
	glPushMatrix();
	glTranslated(0.0, 310.0, 0.0);
	glColor3d(0, 1, 0);
	glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, 'Y');
	glPopMatrix();
	
	glPushMatrix();
	glTranslated(0.0, 0.0, 310.0);
	glColor3d(0, 0, 1);
	glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, 'Z');
	glPopMatrix();
}

void reshape(int width, int height) {
	if (height <= 0) {
		height = 1;
	}
	glViewport(0, 0, (GLsizei)width, (GLsizei)height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(48.6, (GLdouble)width / (GLdouble)height, 3.0, 10000.0);
	glMatrixMode(GL_MODELVIEW);
}

void display() {
	if (Running) {
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glLoadIdentity();
		if (PreDisplayFunctionPointer) {
			PreDisplayFunctionPointer();
		}
		camera();
		drawOrigin();
		if (TransformFunctionPointer) {
			TransformFunctionPointer();
		}
		if (DrawFunctionPointer) {
			DrawFunctionPointer();
		}
		glutSwapBuffers();	
	}
}

void mouseclick(int button, int state, int x, int y) {
	MouseButtons[button] = state;
	MousePrevPos[0] = x;
	MousePrevPos[1] = y;
}

void mousemove(int x, int y) {
	int velX, velY;
	
	// update mouse speed and previous position
	velX = MousePrevPos[0] - x;
	velY = MousePrevPos[1] - y;
	MousePrevPos[0] = x;
	MousePrevPos[1] = y;
	if (MouseButtons[GLUT_LEFT_BUTTON] == GLUT_DOWN) {
		// rotate
		CameraRotate[0] -= (double)velY / 1.5;
		CameraRotate[2] -= (double)velX / 1.5;
	}
	if (MouseButtons[GLUT_MIDDLE_BUTTON] == GLUT_DOWN) {
		// pan
		CameraTransl[0] -= (double)velX * 3.0;
		CameraTransl[1] += (double)velY * 3.0;
	}
	if (MouseButtons[GLUT_RIGHT_BUTTON] == GLUT_DOWN) {
		// "zoom"
		CameraTransl[2] += velY * 10.0;
	}
	if (CameraRotate[0] < -180.0) {
		CameraRotate[0] = -180.0;
	} else if (CameraRotate[0] > 0.0) {
		CameraRotate[0] = 0.0;
	}
}

void keypress(unsigned char key, int x, int y) {
	switch (key) {
	case ' ': // reset camera view
		CameraTransl[0] = CameraTransl[1] = CameraRotate[0] = CameraRotate[1] = CameraRotate[2] = CameraTransl[2]  = 0;
		CameraRotate[0] = -70.0;
		CameraRotate[2] = 30.0;
		CameraTransl[2] = -1100.0;
		break;
	default:
		if (KeyboardFunctionPointer) {
			KeyboardFunctionPointer(key);
		}
		break;
	}
}

void timer(int val) {
	char tmp[100];
	
	sprintf(tmp, "%d", val);
	glutSetWindowTitle(tmp);
	glutPostRedisplay();
	if (Running) {
		glutTimerFunc(50, timer, (val + 1) % 10000);
	} else {
		if (FinalizeCallbackPointer) {
			FinalizeCallbackPointer();
		}
	}
}

void GlutViewer_setEffectsEnabled(bool enabled) {
	EnableEffects = enabled;
	if (enabled) {
		glEnable(GL_BLEND);
		glEnable(GL_FOG);
		glEnable(GL_LIGHT0);
		glEnable(GL_LIGHTING);
		glEnable(GL_COLOR_MATERIAL);
	} else {
		glDisable(GL_BLEND);
		glDisable(GL_FOG);
		glDisable(GL_LIGHT0);
		glDisable(GL_LIGHTING);
		glDisable(GL_COLOR_MATERIAL);
	}
}

void GlutViewer_finalize(void (* callback)(void)) {
	FinalizeCallbackPointer = callback;
	Running = false;
}

void GlutViewer_drawText(const char * fmt, ...) {
	char text[256];
	va_list ap;
	
	if (fmt) {
		va_start(ap, fmt);
		vsprintf(text, fmt, ap);
		va_end(ap);
		glPushAttrib(GL_LIST_BIT);
		glListBase(TextBase - 32);
		glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);
		glPopAttrib();
	}
}

void GlutViewer_run(
  int     * argc, 
  char    * argv[], 
  void    (* drawFunction)(void), 
  void    (* transformFunction)(void), 
  void    (* preDisplayFunction)(void),
  void    (* keyboardFunction)(unsigned char),
  int       windowWidth, 
  int       windowHeight, 
  int       windowLeft, 
  int       windowTop,
  bool		enableEffectsAtStart
  ) 
{
	GLfloat DiffuseMaterial[] = {0.8f, 0.8f, 0.8f};
	GLfloat SpecularMaterial[] = {0.7f, 0.9f, 1.0f};
	GLfloat EmissiveMaterial[] = {0.1f, 0.12f, 0.15f};
	GLfloat mShineness[] = {128};
	GLfloat SpecularLight[] = { 0.7f, 0.9f, 1.0f};
	GLfloat AmbientLight[] = { 0.3f, 0.3f, 0.3f};
	GLfloat DiffuseLight[] = { 0.5f, 0.5f, 0.5f};
	GLfloat SpotLightDirection[] = { 0.0f, 0.0f, 1.0f };
	GLfloat fogDensity = 0.001;
	GLfloat fogColor[4] = {0.0, 0.03, 0.05, 0.2};

	Running = true;
	// reset camera
	keypress(' ', 0, 0);
	// initialize glut
	glutInit(argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
	glutInitWindowSize(windowWidth, windowHeight);
	glutInitWindowPosition(windowLeft, windowTop);
	glutCreateWindow("Viewer");
	// set callback functions
	glutReshapeFunc(reshape);
	glutDisplayFunc(display);
	glutMouseFunc(mouseclick);
	glutMotionFunc(mousemove);
	glutKeyboardFunc(keypress);
	glutTimerFunc(50, timer, 0);
	// OpenGL
	glClearColor(0, 0, 0, 1);
	// setup effects
	EnableEffects = enableEffectsAtStart;
	if (enableEffectsAtStart) {
		glEnable(GL_BLEND);
		glEnable(GL_FOG);
		glEnable(GL_LIGHT0);
		glEnable(GL_LIGHTING);
		glEnable(GL_COLOR_MATERIAL);
	}
	// enable & setup depth
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	// setup text writing
	TextBase = glGenLists(96);
	TextFont = CreateFont(-24, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH, "Courier New");
	TextOldFont = (HFONT)SelectObject(TextHdc, TextFont);
	wglUseFontBitmaps(TextHdc, 32, 96, TextBase);
	SelectObject(TextHdc, TextOldFont);
	DeleteObject(TextFont);
	// setup alpha blend
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA);
	// setup fog
	glFogi(GL_FOG_MODE, GL_EXP2);
	glFogfv(GL_FOG_COLOR, fogColor);
	glFogf(GL_FOG_DENSITY, fogDensity);
	glHint(GL_FOG_HINT, GL_FASTEST);
	// setup lightning
	glShadeModel(GL_SMOOTH);
	glLightfv(GL_LIGHT0, GL_SPECULAR, SpecularLight);
	glLightfv(GL_LIGHT0, GL_AMBIENT, AmbientLight);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, DiffuseLight);
	glMaterialfv(GL_FRONT, GL_SPECULAR, SpecularMaterial);
	glMaterialfv(GL_FRONT, GL_SHININESS, mShineness);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, DiffuseMaterial);
	glMaterialfv(GL_FRONT, GL_EMISSION, EmissiveMaterial);
	// set custom functions
	DrawFunctionPointer = drawFunction;
	TransformFunctionPointer = transformFunction;
	PreDisplayFunctionPointer = preDisplayFunction;
	KeyboardFunctionPointer = keyboardFunction;
	// start glut
	glutMainLoop();
}